{
	const HEX = "0123456789ABCDEF";

	/**
	 * 字节转为 HEX 字符串
	 */
	function byte2hex(byte) {
		return HEX.charAt((byte >> 4) & 0x0F) + HEX.charAt(byte & 0x0F);
	}

	/**
	 * 取字节对应的 ASCII 可显示字符，如果不可显示，则返回 null
	 */
	function byte2ascii(byte) {
		return (byte < 0x20 || byte > 0x7F) ? null : String.fromCharCode(byte);
	}

	/**
	 * 打印输出
	 * @param {number[]} arr 数组
	 * @param {number} offset 数组起始偏移；默认为 0
	 * @param {number} len 输出多少个字节；默认为整个数组长度
	 * @param {number} charsPerLine 每行输出多少个字节；默认为 16
	 * @param {boolean} showLineOffset 是否输出对应行偏移地址；默认为 true
	 * @param {boolean} showAscii 是否输出对应的 ASCII 字符；默认为 true
	 */
	function dump(arr, offset, len, charsPerLine, showLineOffset, showAscii) {
		offset = offset || 0;
		len = len || arr.length;
		len = Math.min((arr.length - offset), len); // 实际有效数据长度
		charsPerLine = charsPerLine || 16;
		if((typeof showLineOffset) !== "boolean") { showLineOffset = true; }
		if((typeof showAscii) !== "boolean") { showAscii = true; }

		for(let i=0; i <= len / charsPerLine; i++) {
			let charCount = len - i*charsPerLine;
			if(charCount > charsPerLine) { charCount = charsPerLine; }
			let str = "";

			if(showAscii) {
				// 输出 HEX
				for(let j=0; j < charsPerLine; j++) {
					str += ((charCount - j) > 0) ? (byte2hex(arr[offset + i*charsPerLine + j]) + " ") : "   ";
				}
				// 输出 ASCII
				for(let j=0; j < charCount; j++) {
					let ch = byte2ascii(arr[offset + i*charsPerLine + j]);
					str += (ch == null) ? "." : ch;
				}
			} else {
				// 输出 HEX
				for(let j=0; j < charCount; j++) {
					str += byte2hex(arr[offset + i*charsPerLine + j]) + " ";
				}
			}

			// 如果需要就输出行偏移地址
			if(showLineOffset) {
				let offset = i*charsPerLine;
				str = byte2hex((offset >> 8) & 0xFF) + byte2hex(offset & 0xFF) + ": " + str;
			}

			// 打印输出
			console.log(str);
		}
	}

	/**
	 * ByteBuffer 字节缓冲区
	 */
	class ByteBuffer {
		/**
		 * 构造函数
		 * @param {int / ArrayBuffer} size_or_buffer 缓冲区大小 或 已有的缓冲区(ArrayBuffer)
		 * @param {int} dataLength (仅当 size_or_buffer 是 ArrayBuffer 类型时)已有的缓冲区中的有效数据长度
		 */
		constructor(size_or_buffer, dataLength) {
			this._littleEndian = false; // 是否是小端模式；默认是大端在前模式(网络字节序)
			this._readMode = false; 	// 读写模式
			this._data = null;			// 内部数据存储 DataView
			this._pos = 0;    			// 当前光标位置
			this._limit = 0;  			// 读模式下表示有效数据长度；写模式下表示缓冲区上限
			this._mark = -1;			// 标记

			if((typeof size_or_buffer) === "number") {
				// 如果参数类型是数字，则表示创建一个新的缓冲区，并设置为写模式
				this._data = new DataView(new ArrayBuffer(size_or_buffer));
				this._readMode = false;
				this._pos = 0;
				this._limit = size_or_buffer;
			} else if(size_or_buffer instanceof ArrayBuffer || size_or_buffer instanceof Uint8Array) {
				// 如果参数类型是 ArrayBuffer，则表示使用当前的缓冲区，并设置为读模式
				this._data = new DataView((size_or_buffer instanceof ArrayBuffer) ? size_or_buffer : size_or_buffer.buffer);
				this._readMode = true;
				this._pos = 0;
				this._limit = Math.min(this.capacity(), dataLength || this.capacity()); // 有效数据长度
			} else {
				throw {code: 400, message: "错误的初始化参数！"};
			}
		}

		// 获取底层的 ArrayBuffer
		raw() { return this._data.buffer; }
		// 压缩并返回(复制)只含有效数据的 ArrayBuffer
		pack() { return this.raw().slice(0, this._readMode ? this._limit : this._pos); }
		// 设置为大端模式
		be() { this._littleEndian = false; return this; }
		// 设置为小端模式
		le() { this._littleEndian = true; return this; }
		// 容量
		capacity() { return this._data.byteLength; }
		// 读模式下是否有数据可读；写模式下是否还有空间可写
		remain() { return (this._limit - this._pos); }
		// 清空缓冲区(同时进入写模式)
		clear() { this._limit = this.capacity(); this._pos = 0; this._readMode = false; return this; }
		// 移动光标到头部
		rewind() { this._pos = 0; return this; }
		// 标记当前位置
		mark() { this._mark = this._pos; return this; }
		// 恢复到标记的位置
		reset() { if(this._mark >= 0) { this._pos = this._mark; } return this; }

		// 带参数时定位光标到指定位置；不带参数时，返回光标当前位置
		position(pos) {
			if(pos === undefined) { return this._pos; }
			if((typeof pos) !== "number") { throw {code: 400, message: "错误的参数！"}; }
			if(pos > this._limit) { throw {code: 400, message: "Out of boundary !"}; }
			this._pos = pos;
			return this;
		}

		// 翻转缓冲区(写模式切换为读模式)
		flip() {
			if(!this._readMode) {
				this._limit = this._pos;
				this._pos = 0;
				this._readMode = true;
			}
			return this;
		}

		/**
		 * 打印输出
		 * @param {number} charsPerLine 每行输出多少个字节；默认为 16
		 * @param {boolean} showLineOffset 是否输出对应行偏移地址；默认为 true
		 * @param {boolean} showAscii 是否输出对应的 ASCII 字符；默认为 true
		 */
		dump(charsPerLine, showLineOffset, showAscii) {
			dump(new Uint8Array(this.raw()), this._pos, this.remain(), charsPerLine, showLineOffset, showAscii);
		}

		// 写之前调用：判断是否有足够空间；如果是读模式，则切换到写模式
		_prewrite(size) {
			if(this._readMode) {
				this._readMode = false;
				this._pos = this._limit; 		// 光标移到最后写的位置
				this._limit = this.capacity();	// limit 移到末尾
			}
			if(size > this.remain()) { throw {code: 500, message: "ByteBuffer overflow !"}; }
		}
		// 读之前调用：检查是否有数据可读
		_preread(size) {
			if(size > this.remain()) { throw {code: 500, message: "ByteBuffer underflow !"}; }
		}

		// Int8 / Uint8
		putInt8(val)    { this._prewrite(1); this._data.setInt8(this._pos++, val); }
		putUint8(val)   { this._prewrite(1); this._data.setUint8(this._pos++, val); }
		getInt8()       { this._preread(1);  return this._data.getInt8(this._pos++); }
		getUint8()      { this._preread(1);  return this._data.getUint8(this._pos++); }

		// Int16 / Uint16
		putInt16(val)   { this._prewrite(2); let pos = this._pos; this._pos += 2; this._data.setInt16(pos, val,     this._littleEndian); }
		putUint16(val)  { this._prewrite(2); let pos = this._pos; this._pos += 2; this._data.setUint16(pos, val,    this._littleEndian); }
		getInt16()      { this._preread(2);  let pos = this._pos; this._pos += 2; return this._data.getInt16(pos,   this._littleEndian); }
		getUint16()     { this._preread(2);  let pos = this._pos; this._pos += 2; return this._data.getUint16(pos,  this._littleEndian); }

		// Int32 / Uint32
		putInt32(val)   { this._prewrite(4); let pos = this._pos; this._pos += 4; this._data.setInt32(pos, val,     this._littleEndian); }
		putUint32(val)  { this._prewrite(4); let pos = this._pos; this._pos += 4; this._data.setUint32(pos, val,    this._littleEndian); }
		getInt32()      { this._preread(4);  let pos = this._pos; this._pos += 4; return this._data.getInt32(pos,   this._littleEndian); }
		getUint32()     { this._preread(4);  let pos = this._pos; this._pos += 4; return this._data.getUint32(pos,  this._littleEndian); }

		// Float32 / Float64
		putFloat32(val) { this._prewrite(4); let pos = this._pos; this._pos += 4; this._data.setFloat32(pos, val,   this._littleEndian); }
		putFloat64(val) { this._prewrite(8); let pos = this._pos; this._pos += 8; this._data.setFloat64(pos, val,   this._littleEndian); }
		getFloat32()    { this._preread(4);  let pos = this._pos; this._pos += 4; return this._data.getFloat32(pos, this._littleEndian); }
		getFloat64()    { this._preread(8);  let pos = this._pos; this._pos += 8; return this._data.getFloat64(pos, this._littleEndian); }

		/**
		 * 写入一个字节数组（每个元素占一个字节）
		 * @param {number[]} arr ArrayBuffer / DataView / number[] 数组
		 * @param {number} fillSize 要填充的字节长度；如果小于数组长度，则只填写指定长度；如果大于数组长度，则剩余部分填写 blankValue；如果未指定，则默认等于数组长度
		 * @param {number} blankValue 空白部分填写的数值(u8)；如果未指定，则默认等于 0x00
		 */
		putByteArray(arr, fillSize, blankValue) {
			let type = (typeof arr);
			let vals = null;

			if(type === "object" && (arr instanceof Array)) {
				vals = arr;
			} else if(type === "object" && (arr instanceof ArrayBuffer)) {
				vals = new Uint8Array(arr);
			} else if(type === "object" && (arr instanceof DataView)) {
				vals = arr;
			} else {
				throw {code: 400, message: "错误的参数类型 !"};
			}

			fillSize = fillSize || vals.length; // 未指定，则默认等于数组长度
			this._prewrite(fillSize);
			blankValue = blankValue || 0x00;    // 未指定，则默认等于 0x00
			if(blankValue > 0xFF) { throw {code: 400, message: "错误的空白字符值 !"}; }

			// 填写数组
			let len = Math.min(vals.length, fillSize);
			for(let i=0; i < len; i++) {
				let ch = vals[i];
				if(ch > 0xFF) { throw {code: 400, message: "数组中含有大于 0xFF 的值 !"}; }
				this._data.setUint8(this._pos++, ch);
			}
			// 填写空白部分(如果有的话)
			len = fillSize - len;
			for(let i=0; i < len; i++) {
				this._data.setUint8(this._pos++, blankValue);
			}
		}

		/**
		 * 读取(复制)一个 Uint8Array
		 * @param {number} length 要读取的长度；默认为从当前位置到缓冲区尾部
		 */
		getByteArray(length) {
			length = length || this.remain();
			this._preread(length);
			return new Uint8Array(this.raw().slice(this._pos, this._pos + length));
		}

		/**
		 * 写入一个 ASCII 字符串(每个字符占一个字节)
		 * @param {string} str 字符串
		 * @param {number} fillSize 要填充的字节长度；如果小于字符串长度，则只填写指定长度；如果大于字符串长度，则剩余部分填写 blankValue；如果未指定，则默认等于字符串长度
		 * @param {number} blankValue 空白部分填写的数值(u8)；如果未指定，则默认等于 0x00
		 */
		putAsciiString(str, fillSize, blankValue) {
			if((typeof str) !== "string") { throw {code: 400, message: "错误的参数 !"}; }
			fillSize = fillSize || str.length; // 未指定，则默认等于字符串长度
			this._prewrite(fillSize);
			blankValue = blankValue || 0x00;   // 未指定，则默认等于 0x00
			if(blankValue > 0xFF) { throw {code: 400, message: "错误的空白字符值 !"}; }

			// 填写字符串
			let len = Math.min(str.length, fillSize);
			for(let i=0; i < len; i++) {
				let ch = str.charCodeAt(i);
				if(ch > 0xFF) { throw {code: 400, message: "字符串中含有 unicode 字符 !"}; }
				this._data.setUint8(this._pos++, ch);
			}
			// 填写空白部分(如果有的话)
			len = fillSize - len;
			for(let i=0; i < len; i++) {
				this._data.setUint8(this._pos++, blankValue);
			}
		}

		/**
		 * 读取一个 ASCII 字符串(遇到 '\0' 就结束)
		 * @param {number} maxLength 最大长度；默认为不限长度，读到缓冲区末尾
		 */
		getAsciiString(maxLength) {
			maxLength = maxLength || this.remain();
			if(this.remain() < maxLength) { throw {code: 500, message: "ByteBuffer underflow !"}; }
			let str = "";
			for(let i=0; i < maxLength; i++) {
				let ch = this.getUint8();
				if(ch == 0) { return str; }
				str += String.fromCharCode(ch);
			}
		}
	};

	// for test only
	function test() {
		let bbuf = new ByteBuffer(24);
		bbuf.putInt8(-1);
		//bbuf.putInt8(0x10);
		bbuf.putInt16(-2);
		//bbuf.putInt16(0x2345);
		bbuf.le().putInt16(0x6789);
		bbuf.putAsciiString("hello", 8);
		bbuf.be().putInt32(0xAABBCCDD);
		bbuf.putByteArray([0x11, 0x12, 0x13, 0x14, 0x15], 6, 0xFF);
		console.log(bbuf);
		bbuf.flip();
		bbuf.dump();
		bbuf.putInt8(0xAB);
		bbuf.flip();
		bbuf.dump();
		console.log(bbuf.getUint8());
		console.log(bbuf.getUint16());
		console.log(bbuf.getInt32());
		console.log(bbuf.getUint32());
		console.log(bbuf.position(5).getAsciiString(6, false));
		bbuf.rewind();
		console.log(bbuf.getFloat32());
		console.log(bbuf.getFloat64());

		let slice = bbuf.getByteArray(6);
		console.log(slice);
		let bs = new ByteBuffer(slice);
		console.log(bs.getUint16());
		console.log(bs.getUint32());
	}
	
	// 导出符号表
	(function(symbols) {
		// 如果是在 CommonJS 兼容环境(如微信小程序/Node)中使用，需要使用如下的方式导出
		if((typeof module !== "undefined")) { module.exports = symbols; return; }
		// 如果是在其它运行环境中使用，把要导出的符号放到全局命名空间中去
		let _gNS_ =	(typeof window !== "undefined") ? window : // 浏览器环境: 导出到 windows 对象中去
					(typeof global !== "undefined") ? global : // ?
					(typeof self   !== "undefined") ? self :   // ?
					null;                                      // 未知运行环境！
		if(!_gNS_) { throw "Unknown how to export symbols !"; }
		// 把要导出的符号放到全局命名空间中去
		for(let key in symbols) { _gNS_[key] = item; }
	})({
		// 要导出的符号表
		byte2hex, byte2ascii, dump, ByteBuffer, test 
	});
}
